/**会话管理工具:
 *  1. 支持会话序列化保存和加载
 *  2. 会话支持配置的生命周期
 * 
 * @author lms 2022.07.22
 */

package ms.core.gateway.tool;

import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import lombok.Data;
import ms.core.db.redis.RedisCliDrv;
import ms.core.gateway.bean.SysCorpBean;
import ms.core.gateway.response.SysUserVO;
import ms.core.gateway.system.Application;
import ms.core.gateway.utils.SysRoleUtils;
import ms.core.tool.Log4j2Tool;
import ms.core.tool.StrTool;
import ms.core.tool.SysTool;

public class SessionTool {
	//private declarations
	private static String redisConf = "/config/redis.properties";
	private final static String CHECK_CODE = "checkCode";
	private static RedisCliDrv redis = null;

	public static void init() {
		redis = RedisCliDrv.getInstance(redisConf);
		Log4j2Tool.info(SessionTool.class, "redis initialized.");
	}

	/**
	 * 判断会话是否登录,用于防止伪造cookie
	 * 
	 * @param sid sessionId of session
	 * @return true-已登录;false-未登录
	 */
	public static boolean logined(String sid){		
		Session ssn = getSession(sid);
		return ((ssn!=null) && (!StrTool.isBlank(ssn.getUserUid())));
	}

	/**
	 * 缓存当前用户验证码到会话
	 * 
	 * @param sid
	 * @param ip
	 * @param code
	 */
	public static void setCheckCode(String sid, String ip, String code){
		Session ssn = getSession(sid);
		if (ssn==null) {
			ssn = new Session();
			ssn.setSid(sid);
			ssn.setFromIp(ip);
			ssn.setStartVisit(new Date());
		}

		ssn.put(CHECK_CODE,code);
		ssn.setLastKeepAlive(new Date());
		setSession(sid, ssn);
	}

	/**
	 * 保存用户登录会话
	 * 
	 * @param sid 会话id
	 * @param usr
	 */
	public static void setLoginSession(String sid, SysUserVO usr) {
		Session ssn = getSession(sid);
		if (ssn==null) ssn = new Session();
		
		ssn.setSid(sid);
		ssn.setCorpUid(usr.getCorpUid());
		ssn.setUserUid(usr.getUid());
		ssn.setAccount(usr.getAccount());
		ssn.setNick(usr.getNick());
		ssn.setCtype(usr.getCtype());
		ssn.setRoot(usr.getRoot());
		ssn.setFromIp(usr.getVisitIp());
		ssn.setRoles(usr.getRoleList());
		ssn.setFuncs(usr.getFuncList());
		ssn.setStartVisit(new Date());

		setSession(sid, ssn);
	}

	/**
	 * 获取会话用户验证码
	 * 如果未建立会话，返回随机数；已建立会话，返回验证码并用随机码重置已有验证码
	 * 
	 * @param sid
	 * @return
	 */
	public static String getCheckCode(String sid){
		Session ssn = getSession(sid);
		if (ssn==null) return SysTool.makeUUID("");

		String code = (String) ssn.get(CHECK_CODE);
		ssn.put(CHECK_CODE, SysTool.makeUUID(""));
		setSession(sid, ssn);
		
		return code;
	}

	/**
	 * 根据会话Id，获取会话对象
	 * @param sid key of session
	 * @return session
	 */
	public static Session getSession(String sid){
		if (StrTool.isBlank(sid)) return null;

		return redis.getObject(sid, Session.class);
	}

	/**
	 * to set session,if sessionId exists, it's value will be replace by session
	 * @param sid key of session id
	 * @param obj session object
	 */
	private static void setSession(String sid, Session ssn){
		redis.setObject(sid, ssn, Application.SESSION_EXPIRY * 60);
	}

	/**
	 * get current login userid
	 * @param sid
	 * @return
	 */
	public static String getUserUid(String sid){
		Session ssn = getSession(sid);
		if (ssn==null) return null;
		
		return ssn.getUserUid();
	}

	/**
	 * get current login user account
	 * @param sid
	 * @return
	 */
	public static String getAccount(String sid){
		Session ssn = getSession(sid);
		if (ssn==null) return "N/A";
		
		return ssn.getAccount();
	}

	/**
	 * get current login user nick
	 * @param sid
	 * @return
	 */
	public static String getNick(String sid){
		Session ssn = getSession(sid);
		if (ssn==null) return "N/A";
		
		return ssn.getNick();
	}

	/**
	 * get current login user type
	 * @param sid
	 * @return
	 */
	public static int getCtype(String sid){
		Session ssn = getSession(sid);
		if (ssn==null) return -1;
		
		return ssn.getCtype();
	}
	
	/**
	 * 判断当前登录帐号是否是平台企业
	 * 
	 * @param sid
	 * @return
	 */
	public static boolean isSuperCorp(String sid) {
		Session ssn = getSession(sid);
		if (ssn==null) return false;

		return (ssn.getCtype() & SysCorpBean.SUPPER_CORP) == SysCorpBean.SUPPER_CORP;
	}

	/**
	 * get current login user root
	 * @param sid
	 * @return
	 */
	public static int getRoot(String sid){
		Session ssn = getSession(sid);
		if (ssn==null) return -1;
		
		return ssn.getRoot();
	}

	/**
	 * get current login corpId
	 * @param sid
	 * @return >0 for success,otherwise return -1 
	 */
	public static String getCorpUid(String sid){
		Session ssn = getSession(sid);
		if (ssn==null) return null;
		
		return ssn.getCorpUid();
	}

	/**
	 * to update user session's data
	 * @param sid
	 * @param data
	 */
	public static void put(String sid, String key, Object data){
		Session ssn = getSession(sid);
		if (ssn==null) return;

		ssn.put(key, data);
		setSession(sid, ssn);
	}

	/**
	 * to get user session's data
	 * @param sid
	 * @param data
	 */
	public static Object get(String sid, String key){
		Session ssn = getSession(sid);
		if (ssn==null) return null;

		return ssn.get(key);
	}

	/**
	 * to update user session's data
	 * @param sid
	 * @param data
	 */
	public static void updateLastVisit(String sid, String ip){
		Session ssn = getSession(sid);
		if (ssn==null) return;

		ssn.setFromIp(ip);
		ssn.setLastKeepAlive(new Date());
		setSession(sid, ssn);
	}

	/**
	 * 在线用户企业类型变更
	 * 
	 * @param corpUid 企业id
	 * @param ctype 企业类型
	 */
	public static void updateCorpType(String corpUid, int ctype) {
		Set<String> keys = redis.getKeys("sid.*");
		if (keys==null || keys.size()==0) return;

		Iterator<String> it = keys.iterator();
		while (it.hasNext()) {
			String sid = it.next();
			Session ssn = getSession(sid);
			// 非对应企业用户
			if (ssn==null || !corpUid.equals(ssn.getCorpUid())) continue;

			// 更新企业类型
			ssn.setCtype(ctype);
			setSession(sid, ssn);
		}
	}
	
	/**
	 * 在线用户role名称变更(角色权限不变)
	 * 
	 * @param corpUid 企业id
	 * @param oldRoleName 原角色名
	 * @param newRoleName 新角色名
	 */
	public static void updateCorpUserRole(String corpUid, String oldRoleName, String newRoleName) {
		Set<String> keys = redis.getKeys("sid.*");
		if (keys==null || keys.size()==0) return;

		Iterator<String> it = keys.iterator();
		while (it.hasNext()) {
			String sid = it.next();
			Session ssn = getSession(sid);
			// 非对应企业用户
			if (ssn==null || !corpUid.equals(ssn.getCorpUid())) continue;

			// 没有包含被删除的角色
			List<String> roles = ssn.getRoles();
			if (roles==null || !roles.contains(oldRoleName)) continue;

			// 更新角色
			roles.remove(oldRoleName);
			roles.add(newRoleName);
			ssn.setRoles(roles);
			setSession(sid, ssn);
		}
	}

	/**
	 * 刷新在线用户权限(用户授予角色)
	 * 
	 * @param corpUid  企业uid
	 * @param userUid 用户id
	 * @param roleName 角色名
	 */
	public static void refreshCorpUserRoleFuncs(String corpUid, String userUid, List<String> roles) {
		Set<String> keys = redis.getKeys("sid.*");
		if (keys==null || keys.size()==0) return;

		Iterator<String> it = keys.iterator();
		while (it.hasNext()) {
			String sid = it.next();
			Session ssn = getSession(sid);
			// 非对应用户
			if (ssn==null || !userUid.equals(ssn.getUserUid())) continue;

			// 设置角色功能
			ssn.setFuncs(SysRoleUtils.getRoleFuncs(corpUid, roles));
			setSession(sid, ssn);
		}
	}	
	/**
	 * 刷新在线用户权限(role权限变更)
	 * 
	 * @param corpUid 企业id
	 * @param roleName 角色名
	 */
	public static void refreshCorpUserRoleFuncs(String corpUid, String roleName) {
		Set<String> keys = redis.getKeys("sid.*");
		if (keys==null || keys.size()==0) return;

		Iterator<String> it = keys.iterator();
		while (it.hasNext()) {
			String sid = it.next();
			Session ssn = getSession(sid);
			// 非对应企业用户
			if (ssn==null || !corpUid.equals(ssn.getCorpUid())) continue;

			// 没有包含权限变更角色
			List<String> roles = ssn.getRoles();
			if (roles==null || !roles.contains(roleName)) continue;

			ssn.setFuncs(SysRoleUtils.getRoleFuncs(corpUid, roles));
			setSession(sid, ssn);
		}
	}

	/**
	 * 移除在线用户拥有对应role的权限
	 * 
	 * @param corpUid 企业id
	 * @param roleName 角色名
	 */
	public static void removeCorpUserRoleFuncs(String corpUid, String roleName) {
		Set<String> keys = redis.getKeys("sid.*");
		if (keys==null || keys.size()==0) return;

		Iterator<String> it = keys.iterator();
		while (it.hasNext()) {
			String sid = it.next();
			Session ssn = getSession(sid);
			// 非对应企业用户
			if (ssn==null || !corpUid.equals(ssn.getCorpUid())) continue;
			
			// 没有包含被删除的角色
			List<String> roles = ssn.getRoles();
			if (roles==null || !roles.contains(roleName)) continue;
			
			// 移除角色，并刷新权限
			roles.remove(roleName);
			ssn.setRoles(roles);
			ssn.setFuncs(SysRoleUtils.getRoleFuncs(corpUid, roles));
			setSession(sid, ssn);
		}
	}

	/**
	 * 按会话Id删除会话
	 * @param sessionId key of the session
	 */
	public static void invalidSession(String sid){
		if (!StrTool.isBlank(sid)) {
			redis.remove(sid);
		}
	}

	/**
	 * 按用户删除会话
	 * 
	 * @param userUid
	 */
	public static void invalidUserSession(String userUid) {
		Set<String> keys = redis.getKeys("sid.*");
		if (keys==null || keys.size()==0) return;

		Iterator<String> it = keys.iterator();
		while (it.hasNext()) {
			String sid = it.next();
			Session ssn = getSession(sid);
			// 非对应用户
			if (ssn==null || !userUid.equals(ssn.getUserUid())) continue;

			invalidSession(sid);
			break;
		}
	}
	
	/**
	 * 按企业删除会话
	 * 
	 * @param corpUid
	 */
	public static void invalidCorpSession(String corpUid) {
		Set<String> keys = redis.getKeys("sid.*");
		if (keys==null || keys.size()==0) return;

		Iterator<String> it = keys.iterator();
		while (it.hasNext()) {
			String sid = it.next();
			Session ssn = getSession(sid);
			// 非对应企业用户
			if (ssn==null || !corpUid.equals(ssn.getCorpUid())) continue;
			
			invalidSession(sid);
		}
	}
	
	/**
	 * 设置api授权会话信息
	 * 
	 * @param clientId
	 * @param ssn
	 */
	public static void setAuthSession(String clientId, AuthSession ssn) {
		redis.setObject(clientId, ssn, Application.SESSION_EXPIRY * 60);
	}

	/**
	 * 根据会话Id，获取会话对象
	 * 
	 * @param clientId 客户端Id
	 * @return
	 */
	public static AuthSession getAuthSession(String clientId){
		if (StrTool.isBlank(clientId)) return null;

		return redis.getObject(clientId, AuthSession.class);
	}
	
	/**
	 * 在线用户会话对象
	 * by @author lms 2022.07.27
	 */
	@Data
	public static class Session {
		//session information
		private String sid;							// 会话Id
		private String userUid;						// 用户帐号Id
		private String corpUid;						// 企业id
		private int ctype;							// 用户所属企业类型
		private String account;						// 用户帐号
		private int root;							// 主账号标识(0-非主账号;1-主账号)
		private String nick;						// 用户昵称
		private String fromIp;						// 访问IP来源
		private Date startVisit;					// 开始访问时间
		private Date lastKeepAlive;					// 最后一次会话握手
		private List<String> roles;					// 用户角色列表,格式["roleA","roleB",...]
		private List<String> funcs;					// 用户功能列表,格式 ["fun1","fun2",...]
		private Map<String, Object> ext;			// 保存扩展信息

		public void put(String key, Object data) {
			if (ext==null) ext = new HashMap<>();
			ext.put(key, data);
		}

		public Object get(String key) {
			if (ext==null) return null;
			return ext.get(key);
		}
	}

	/**
	 * api 授权企业在线对象
	 * 
	 * @author lms
	 *
	 */
	@Data
	public static class AuthSession {
		private String clientId;		// 授权clientId
		private String token;			// 访问token
		private List<String> funcs;		// 可访问api列表
		private int state;				// 授权状态
		private Date limitTime;			// 到期时间
	}
}